﻿using System;
#if !NET3_5
using System.Collections.Concurrent;
#else
using System.Threading;
using System.Collections.Generic;
#endif
using System.Linq.Expressions;
using System.Reflection;

namespace ObjectMapper
{
    internal class FastField<T, TField>
    {
#if !NET3_5
        private static readonly ConcurrentDictionary<string, FastField<T, TField>> _fieldCache =
            new ConcurrentDictionary<string, FastField<T, TField>>();

        private readonly Lazy<Func<T, TField>> _getter;
        private readonly Lazy<Action<T, TField>> _setter;
#else
        private static readonly Dictionary<string, FastField<T, TField>> _fieldCache =
            new Dictionary<string, FastField<T, TField>>();

        private Func<T, TField> _getter;
        private Action<T, TField> _setter;

        private readonly object _getterLock = new object();
        private readonly object _setterLock = new object();
#endif

        private FastField(FieldInfo field)
        {
            if (field == null)
            {
                throw new ArgumentNullException("field");
            }
            Field = field;
#if !NET3_5
            _getter = new Lazy<Func<T, TField>>(() => GetGetMethod(field));
            _setter = new Lazy<Action<T, TField>>(() => GetSetMethod(field));
#endif
        }

        public FieldInfo Field { get; private set; }

        public TField Get(T instance)
        {
#if !NET3_5
            return _getter.Value(instance);
#else
            if (_getter == null)
            {
                Thread.MemoryBarrier();
                lock (_getterLock)
                {
                    if (_getter == null)
                    {
                        _getter = GetGetMethod(Field);
                    }
                }
            }
            return _getter(instance);
#endif
        }

        public void Set(T instance, TField value)
        {
#if !NET3_5
            _setter.Value(instance, value);
#else
            if (_setter == null)
            {
                Thread.MemoryBarrier();
                lock (_setterLock)
                {
                    if (_setter == null)
                    {
                        _setter = GetSetMethod(Field);
                    }
                }
            }
            _setter(instance, value);
#endif
        }

        private static Action<T, TField> GetSetMethod(FieldInfo field)
        {
            ParameterExpression instance = Expression.Parameter(typeof(T), "instance");
            ParameterExpression value = Expression.Parameter(typeof(TField), "value");
            Expression instanceCast = typeof(T).IsValueType || typeof(T).IsSealed
                ? (Expression)instance
                : Expression.TypeAs(instance, typeof(T));
            Expression valueCast = field.FieldType != typeof(TField)
                ? field.FieldType.IsValueType
                    ? Expression.Convert(value, field.FieldType)
                    : Expression.TypeAs(value, field.FieldType)
                : (Expression)value;

            MemberExpression fieldExpression = Expression.Field(field.IsStatic ? null : instanceCast, field);
#if NET3_5
            BinaryExpression call = Helper.Assign(fieldExpression, valueCast);
#else
            BinaryExpression call = Expression.Assign(fieldExpression, valueCast);
#endif
            return Expression.Lambda<Action<T, TField>>(call, instance, value).Compile();
        }

        private static Func<T, TField> GetGetMethod(FieldInfo field)
        {
            ParameterExpression instance = Expression.Parameter(typeof(T), "instance");
            Expression instanceCast = typeof(T).IsValueType || typeof(T).IsSealed
                ? (Expression)instance
                : Expression.TypeAs(instance, typeof(T));

            MemberExpression call = Expression.Field(field.IsStatic ? null : instanceCast, field);
            Expression callCast = field.FieldType != typeof(TField)
                ? typeof(TField).IsValueType
                    ? Expression.Convert(call, typeof(TField))
                    : Expression.TypeAs(call, typeof(TField))
                : (Expression)call;
            return Expression.Lambda<Func<T, TField>>(callCast, instance).Compile();
        }

        public static FastField<T, TField> Get(string name)
        {
#if !NET3_5
            return _fieldCache.GetOrAdd(name, CreateField);
#else
            if (!_fieldCache.ContainsKey(name))
            {
                lock (_fieldCache)
                {
                    if (!_fieldCache.ContainsKey(name))
                    {
                        _fieldCache.Add(name, CreateField(name));
                    }
                }
            }
            return _fieldCache[name];
#endif
        }

        private static FastField<T, TField> CreateField(string name)
        {
            FieldInfo info = FastField.GetField(typeof(T), name);
            return info == null ? null : new FastField<T, TField>(info);
        }
    }
}